package cc.jq1024.infrastructure.persistent.repository;

import cc.jq1024.domain.award.model.valobj.AccountStatusVO;
import cc.jq1024.domain.credit.model.aggregate.TradeAggregate;
import cc.jq1024.domain.credit.model.entity.CreditAccountEntity;
import cc.jq1024.domain.credit.model.entity.CreditOrderEntity;
import cc.jq1024.domain.credit.model.entity.CreditTaskEntity;
import cc.jq1024.domain.credit.repository.ICreditRepository;
import cc.jq1024.infrastructure.event.EventPublisher;
import cc.jq1024.infrastructure.persistent.dao.ITaskDao;
import cc.jq1024.infrastructure.persistent.dao.IUserCreditAccountDao;
import cc.jq1024.infrastructure.persistent.dao.IUserCreditOrderDao;
import cc.jq1024.infrastructure.persistent.po.TaskPO;
import cc.jq1024.infrastructure.persistent.po.UserCreditAccountPO;
import cc.jq1024.infrastructure.persistent.po.UserCreditOrderPO;
import cc.jq1024.infrastructure.persistent.redis.IRedisService;
import cc.jq1024.router.strategy.IDBRouterStrategy;
import cc.jq1024.types.common.Constants;
import cc.jq1024.types.enums.ResponseCode;
import cc.jq1024.types.exception.AppException;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 积分仓储实现
 *
 * @author li--jiaqiang
 * @date 2024−07−16
 */
@Slf4j
@Repository
public class CreditRepository implements ICreditRepository {

    @Resource
    private IRedisService redisService;
    @Resource
    private IUserCreditAccountDao userCreditAccountDao;
    @Resource
    private IUserCreditOrderDao userCreditOrderDao;
    @Resource
    private ITaskDao taskDao;
    @Resource
    private IDBRouterStrategy dbRouter;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private ThreadPoolExecutor executor;
    @Resource
    private EventPublisher eventPublisher;

    /**
     * 保存积分交易订单
     *
     * @param tradeAggregate 积分交易聚合对象
     */
    @Override
    public void saveUserCreditTradeOrder(TradeAggregate tradeAggregate) {
        // 获取基本信息
        String userId = tradeAggregate.getUserId();
        CreditAccountEntity creditAccountEntity = tradeAggregate.getCreditAccountEntity();
        CreditOrderEntity creditOrderEntity = tradeAggregate.getCreditOrderEntity();
        CreditTaskEntity taskEntity = tradeAggregate.getTaskEntity();

        // 积分账户
        UserCreditAccountPO userCreditAccountReq = new UserCreditAccountPO();
        userCreditAccountReq.setUserId(userId);
        userCreditAccountReq.setAccountStatus(AccountStatusVO.open.getCode());
        userCreditAccountReq.setTotalAmount(creditAccountEntity.getAdjustAmount());
        // 知识；仓储往上有业务语义，仓储往下到 dao 操作是没有业务语义的。所以不用在乎这块使用的字段名称，直接用持久化对象即可。
        userCreditAccountReq.setAvailableAmount(creditAccountEntity.getAdjustAmount());

        // 积分订单
        UserCreditOrderPO userCreditOrderReq = UserCreditOrderPO.builder()
                .userId(creditOrderEntity.getUserId())
                .orderId(creditOrderEntity.getOrderId())
                .tradeName(creditOrderEntity.getTradeName().getName())
                .tradeType(creditOrderEntity.getTradeType().getCode())
                .tradeAmount(creditOrderEntity.getTradeAmount())
                .outBusinessNo(creditOrderEntity.getOutBusinessNo())
                .build();

        // 任务对象
        TaskPO task = TaskPO.builder()
                .userId(taskEntity.getUserId())
                .topic(taskEntity.getTopic())
                .messageId(taskEntity.getMessageId())
                .message(JSON.toJSONString(taskEntity.getMessage()))
                .state(taskEntity.getState().getCode())
                .build();

        // 分布式锁
        RLock lock = redisService.getLock(Constants.RedisKey.USER_CREDIT_ACCOUNT_LOCK + userId + Constants.UNDERLINE + creditOrderEntity.getOutBusinessNo());
        try {

            lock.lock(3, TimeUnit.SECONDS);
            dbRouter.doRouter(userId);
            // 编程式事务
            transactionTemplate.execute(status -> {
                try {
                    // 1. 保存账户积分
                    UserCreditAccountPO userCreditAccount = userCreditAccountDao.queryUserCreditAccount(userCreditAccountReq);
                    if (null == userCreditAccount) {
                        userCreditAccountDao.insert(userCreditAccountReq);
                    } else {
                        // 获取要调整的积分额度
                        BigDecimal availableAmount = userCreditAccountReq.getAvailableAmount();
                        if (availableAmount.compareTo(BigDecimal.ZERO) >= 0) {
                            // > 0 新增
                            userCreditAccountDao.updateAddAmount(userCreditAccountReq);
                        } else {
                            // < 0 扣减
                            int subtractionCount = userCreditAccountDao.updateSubtractionAmount(userCreditAccountReq);
                            if (1 != subtractionCount) {
                                status.setRollbackOnly();
                                throw new AppException(ResponseCode.USER_CREDIT_ACCOUNT_NO_AVAILABLE_AMOUNT.getCode(), ResponseCode.USER_CREDIT_ACCOUNT_NO_AVAILABLE_AMOUNT.getInfo());
                            }
                        }
                    }
                    // 2. 保存账户订单
                    userCreditOrderDao.insertOne(userCreditOrderReq);
                    // 3. 写入任务
                    taskDao.insertOne(task);
                } catch (DuplicateKeyException e) {
                    status.setRollbackOnly();
                    log.error("调整账户积分额度异常，唯一索引冲突 userId:{} orderId:{}", userId, creditOrderEntity.getOrderId(), e);
                } catch (Exception e) {
                    status.setRollbackOnly();
                    log.error("调整账户积分额度失败 userId:{} orderId:{}", userId, creditOrderEntity.getOrderId(), e);
                }
                return 1;
            });
        } finally {
            dbRouter.clear();
            lock.unlock();
        }

        // 异步发送MQ task 任务
        executor.execute(() -> {
            try {
                // 发送消息【在事务外执行，如果失败还有任务补偿】
                eventPublisher.publish(task.getTopic(), task.getMessage());
                // 更新数据库记录，task 任务表
                taskDao.updateTaskSendMessageCompleted(task);
                log.info("调整账户积分记录，发送MQ消息完成 userId: {} orderId:{} topic: {}", userId, creditOrderEntity.getOrderId(), task.getTopic());
            } catch (Exception e) {
                log.error("调整账户积分记录，发送MQ消息失败 userId: {} topic: {}", userId, task.getTopic());
                taskDao.updateTaskSendMessageFail(task);
            }
        });

    }


    /**
     * 查询用户积分账户余额
     *
     * @param userId 用户ID
     * @return 返回结果
     */
    @Override
    public CreditAccountEntity queryUserCreditAccount(String userId) {
        UserCreditAccountPO userCreditAccountReq = new UserCreditAccountPO();
        userCreditAccountReq.setUserId(userId);
        try {
            dbRouter.doRouter(userId);
            UserCreditAccountPO userCreditAccount = userCreditAccountDao.queryUserCreditAccount(userCreditAccountReq);
            BigDecimal availableAmount = BigDecimal.ZERO;
            if (null != userCreditAccount) {
                availableAmount = userCreditAccount.getAvailableAmount();
            }
            return CreditAccountEntity.builder().userId(userId).adjustAmount(availableAmount).build();
        } finally {
            dbRouter.clear();
        }

    }
}